#ifndef __TCP_SERVER__
#define __TCP_SERVER__
#include "Socket.hpp"
#include "Epoller.hpp"
#include "NoCopy.hpp"
#include <memory>
#include <functional>
#include <unordered_map>

class Connection;
class TcpServer;
using fun_t = std::function<void(std::weak_ptr<Connection>)>;

class Connection
{
public:
    Connection(uint16_t socket, std::weak_ptr<TcpServer> ptr)
        :_socket(socket), 
        _tcpServerPtr(ptr)
    {

    }

    void setHandler(fun_t recbCb, fun_t sendCb, fun_t exceCb)
    {
        _recvCb = recbCb;
        _sendCb = sendCb;
        _exceCb = exceCb;
    }

    void appendInBuffer(const char* info) 
    {
        _inBuffer += info;
    }

    void appendOutBuffer(const char* info) 
    {
        _outBuffer += info;
    }

    int getSocket()
    {
        return _socket;
    }

    std::string& getInBuffer()
    {
        return _inBuffer;
    }

    std::string& getOutBuffer()
    {
        return _outBuffer;
    }
private:
    int _socket;
    std::string _inBuffer;       // 输入缓冲区
    std::string _outBuffer;      // 输出缓冲区
public:
    fun_t _recvCb;               // 接受数据回调
    fun_t _sendCb;               // 发送数据回调
    fun_t _exceCb;               // 异常回调

    // 添加一个回指 指针
    std::weak_ptr<TcpServer> _tcpServerPtr;
};

// enable_shared_from_this: 可以提供返回当前对象的this对应的shared_ptr
class TcpServer : public NoCopy, public std::enable_shared_from_this<TcpServer>
{
    const static uint32_t NUM          = 64;                     // 事件数组的长度
    const static uint32_t EVENT_IN     = EPOLLIN  | EPOLLET;     // ET模式的可读事件
    const static uint32_t EVENT_OUT    = EPOLLOUT | EPOLLET;     // ET模式的可读事件
    const static uint16_t DEFAULT_PORT = 9000;
    
public:
    TcpServer(uint16_t port, fun_t onMessage) : 
    _port(port), _quit(true), _onMessage(onMessage)
    {
        try{
            // 创建Epoller
            _epollerPtr = std::make_shared<Epoller>();
            // 初始化监听套接字
            _listenSocketPtr = std::make_shared<Sock>();
            // 创建监听套接字
            _listenSocketPtr->Socket();
            // 将该套接字设置为非阻塞
            setNoBlock(_listenSocketPtr->GetFd());
        } catch(const std::exception& e) {
            throw;
        }
    }

    ~TcpServer()
    {
        log(INFO, "~EpollServer(), now close _listenSocket: %d.\n", _listenSocketPtr->GetFd());
        _listenSocketPtr->Close();
    }

    void initServer()
    {
        // 允许套接字绑定到一个处于 TIME_WAIT 状态的端口
        int reuse = 1;
        setsockopt(_listenSocketPtr->GetFd(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &reuse, sizeof(reuse));
        _listenSocketPtr->Bind(_port);
        _listenSocketPtr->Listen();
        log(INFO, "Create listen socket done: %d.\n", _listenSocketPtr->GetFd());
        _quit = false;
    }

    void runServer()
    {
        // 让服务器关心监听套接字上的读事件。绑定了回调函数
        // _epollerPtr->epollerCtl(EPOLL_CTL_ADD, _listenSocketPtr->GetFd(), EVENT_IN);
        addConnection(_listenSocketPtr->GetFd(), EVENT_IN, 
        std::bind(&TcpServer::acceptLink, this, std::placeholders::_1), nullptr, nullptr);
        while(!_quit) {
            // 等待事件发生，如果发生了，将事件放到recEvents数组中
            int length = _epollerPtr->epollerWait(_recEvents, NUM);
            switch (length)
            {
            case 0:
                log(INFO, "Time out.\n");
                break;
            case -1:
                log(ERROR, "Epoller wait error, why: \n", strerror(errno));
                throw std::runtime_error("Epoller wait failed");
            default:
                handlerEvent(length);
                break;
            }
            printData();
        }
        _quit = true; 
    }

    void handlerEvent(int length)
    {
        // log(DEBUG, "void handlerEvent(int length)\n");
        for(size_t i = 0; i < length; ++i) {
            int nowFd = _recEvents[i].data.fd;
            uint32_t event = _recEvents[i].events;
            if((event & EPOLLIN) && isConnectionSafe(nowFd)) {
                // 处理读事件, 读事件的回调函数被设置了，就调用
                if(_connection[nowFd]->_recvCb)
                    _connection[nowFd]->_recvCb(_connection[nowFd]);
            } else if((event & EPOLLOUT) && isConnectionSafe(nowFd)) {
                // 处理写事件, 读事件的回调函数被设置了，就调用
                if(_connection[nowFd]->_sendCb)
                    _connection[nowFd]->_sendCb(_connection[nowFd]);
            } else {
                // 其它事件默认进入异常处理
                _connection[nowFd]->_recvCb(_connection[nowFd]);
                log(INFO, "fd: %d has other event.\n", nowFd);
            }
        }
    }
private:
    uint16_t                                             _port;
    std::shared_ptr<Epoller>                             _epollerPtr;
    std::shared_ptr<Sock>                                _listenSocketPtr;
    std::unordered_map<int, std::shared_ptr<Connection>> _connection;       // 维护一张fd-Connection表
    struct epoll_event                                   _recEvents[NUM];
    bool                                                 _quit;             // 是否退出, 在startSerever中用到
    fun_t                                                _onMessage;        // 上层处理数据回调

public:
    bool setNoBlock(int fd)
    {
        // 1.使用F_GETFL将当前的文件描述符的属性取出来
        int fl = fcntl(fd, F_GETFL);
        // 2. 差错处理
        if(fl < 0) {
            log(ERROR, "fcntl error! why: ", strerror(errno));
            throw std::runtime_error("fcntl failed");
        }
        // 3.然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数
        fcntl(fd, F_SETFL, fl | O_NONBLOCK);
        log(INFO, "Set no block done.\n");
        return true;
    }

    // 添加连接
    void addConnection(int socket, uint32_t event, fun_t recvFun, fun_t sendFun, fun_t exceFun)
    {
        // 创建connection对象
        std::shared_ptr<Connection> con = std::make_shared<Connection>(socket, shared_from_this());
        con->setHandler(recvFun, sendFun, exceFun);
        // 添加到哈希表中
        _connection.insert({socket, con});
        // 添加到内核中，让epoll关心
        _epollerPtr->epollerCtl(EPOLL_CTL_ADD, socket, event);
    }

    // 在哈希表中是否有该fd
    bool isConnectionSafe(int fd)
    {
        auto iter = _connection.find(fd);
        if(iter == _connection.end())    return false;
        return true;
    }

    // 创建连接(监听套接字使用)
    void acceptLink(std::weak_ptr<Connection> conn)
    {        
        auto realConn = conn.lock();
        if(realConn == nullptr) {
            log(WARNING, "In acceptLink(), Object no longer exists.\n");
            return;
        }
        for(; ;) {
            std::string ip;
            uint16_t port;
            int newFd = _listenSocketPtr->Accept(&ip, &port);
            if(newFd > 0) {
                // 设置非阻塞
                setNoBlock(newFd);
                // 让epoll关心该套接字, 这些都是非监听套接字, 当有事件后, 执行这里设置的回调函数
                addConnection(newFd, EVENT_IN, 
                std::bind(&TcpServer::readData,       this, std::placeholders::_1), 
                std::bind(&TcpServer::writeData,      this, std::placeholders::_1), 
                std::bind(&TcpServer::exceptionAAAAA, this, std::placeholders::_1));
                log(INFO, 
                "Server get a new link, client ip: %s, client port: %d, fd: %d. Ready to read!\n", ip.c_str(), port, newFd);
            } else {
                if(errno == EWOULDBLOCK) {
                    // 设置非阻塞的时候数据没有准备好
                    // log(INFO, "Data not ready, try again.\n");
                    break;
                } else if(errno == EINTR) {
                    // 被信号打断
                    // log(INFO, "The receive was interrupted by delivery of a signal before any data was available.\n");
                    continue;
                } else {
                    log(WARNING, "Set nonblock other situation.\n");
                    break;
                }
            }   
        }
    }

    // 接受数据
    void readData(std::weak_ptr<Connection> conn)
    {
        auto realConn = conn.lock();
        if(realConn == nullptr) {
            log(WARNING, "In readData(), Object no longer exists\n");
            return;
        }
        // log(DEBUG, "void readData(), fd: %d\n", realConn->getSocket());
        char buff[NUM];
        int socket = realConn->getSocket();
        for(; ;) {
            memset(buff, 0, sizeof buff);
            ssize_t n = recv(socket, buff, sizeof buff, 0);
            if(n > 0) {
                // 放到接受缓冲区中
                realConn->appendInBuffer(buff);
            } else if(n == 0) {
                // 进行异常处理
                log(INFO, "Recv done, fd: %d\n", socket);
                exceptionAAAAA(realConn);
                break;
            } else {
                // 进行异常处理, 这里有三种情况
                if(errno == EWOULDBLOCK)    break;
                else if(errno == EINTR)     break;
                else {
                    log(WARNING, "Recv error! fd: %d\n", socket);
                    exceptionAAAAA(realConn);
                    return;
                }
            }
        }
        // 读取完数据后, 上层进行处理。该回调函数目前在main.cc里面被设置
        _onMessage(realConn);
    }

    // 写回数据
    void writeData(std::weak_ptr<Connection> conn)
    {
        /*
        对于像epoll/select/poll这样的多路转接的程序, 发送缓冲区经常是有空间的, 所以写事件经常是就绪的
        如果我们设置对EPOLLOUT常关心, 那么EPOLLOUT几乎每次都有就绪, 这回导致函数经常返回, 浪费CPU资源.
        所以结论: 对于读事件, 设置常关心, 对于写事件, 按需设置. 具体来说, 需要写时, 直接写入, 如果写入完成,
        就结束. 如果写入完成, 但是数据没有写完, outBuffer里还有内容, 就需要设置对写事件进行关心, 
        如果outBuffder里写完了, 就去除掉对写事件的关心.
        */
        auto realConn = conn.lock();
        if(realConn == nullptr) {
            log(WARNING, "In writeData(), Object no longer exists.\n");
            return;
        }
        int fd = realConn->getSocket();
        std::string& outBuffer = realConn->getOutBuffer();
        for(; ;) {
            ssize_t n = send(fd, outBuffer.c_str(), outBuffer.size(), 0);
            if(n > 0) {
                // 发送成功, 从发送缓冲区移除数据, 如果没有了, 就break
                outBuffer.erase(0, n);
                if(outBuffer.empty())    break;
            } else if(n == 0) {
                // 没有数据要发送
                return;
            } else {
                if(errno == EWOULDBLOCK)    break;
                else if(errno == EINTR)     break;
                else                        return;
            }
        }
        // 跳出循环之后, 如果发送缓冲区还有数据, 说明数据没有发完, 需要设置对写事件进行关心
        if(outBuffer.size()) {
            // 开启对写事件的关心
            enableEvent(fd, true, true);
        } else {
            // 关闭对写事件的关心
            enableEvent(fd, true, false);
        }
    }

    // 异常处理, 读写异常, 或者其它事件都会走这里
    void exceptionAAAAA(std::weak_ptr<Connection> conn)
    {
        auto realConn = conn.lock();
        if(realConn == nullptr) {
            log(WARNING, "In exceptionAAAAA(), Object no longer exists.\n");
            return;
        }
        // log(DEBUG, "void exceptionAAAAA()\n");
        int fd = realConn->getSocket();
        // 取消epoll关心
        _epollerPtr->epollerCtl(EPOLL_CTL_DEL, fd, 0);
        // 关闭文件描述符
        close(fd);
        // 从哈希表中去掉
        _connection.erase(fd);
        log(INFO, "Exception done, close fd: %d\n", fd);
    }

    // 给fd添加读事件或者写事件
    void enableEvent(int fd, bool rEvent, bool wEvent)
    {
        uint32_t event = 0;
        event |= ((rEvent == true ? EVENT_IN : 0) |  (wEvent == true ? EVENT_OUT : 0));
        _epollerPtr->epollerCtl(EPOLL_CTL_MOD, fd, event);
    }

    // 打印哈希表中的数据
    void printData()
    {
        // 打印fd
        std::cout << "fds: ";
        for(const auto[fd, conn] : _connection) {
            std::cout << fd << ' ';
        }
        std::cout << '\n';

        // 打印输入缓冲区
        std::cout << "Inbuffer: ";
        for(const auto[fd, conn] : _connection) {
            std::cout << conn->getInBuffer();
        }
        // std::cout << '\n';
    }
};

#endif